﻿Imports System
Imports System.Collections.Generic
Imports System.Workflow.ComponentModel
Imports System.Workflow.Runtime

Namespace System.Workflow.Activities
    <Serializable> _
    Friend Class StateMachineSubscriptionManager
        ' Methods
        Friend Sub New(ByVal executionState As StateMachineExecutionState, ByVal instanceId As Guid)
            Me._executionState = executionState
            Me._setStateSubscription = New SetStateSubscription(instanceId)
        End Sub

        Friend Shared Sub ChangeEventDrivenQueueState(ByVal context As ActivityExecutionContext, ByVal eventDriven As EventDrivenActivity, ByVal enabled As Boolean)
            Dim queueName As IComparable = StateMachineSubscriptionManager.GetQueueName(StateMachineHelpers.GetEventActivity(eventDriven))
            If (Not queueName Is Nothing) Then
                Dim workflowQueue As WorkflowQueue = StateMachineSubscriptionManager.GetWorkflowQueue(context, queueName)
                If (Not workflowQueue Is Nothing) Then
                    workflowQueue.Enabled = enabled
                End If
            End If
        End Sub

        Private Shared Sub ChangeStateWorkflowQueuesState(ByVal context As ActivityExecutionContext, ByVal state As StateActivity, ByVal enabled As Boolean)
            Dim activity As Activity
            For Each activity In state.EnabledActivities
                Dim eventDriven As EventDrivenActivity = [TryCast](activity,EventDrivenActivity)
                If (Not eventDriven Is Nothing) Then
                    StateMachineSubscriptionManager.ChangeEventDrivenQueueState(context, eventDriven, enabled)
                End If
            Next
        End Sub

        Friend Sub CreateSetStateEventQueue(ByVal context As ActivityExecutionContext)
            Me.SetStateSubscription.CreateQueue(context)
            Me.Subscriptions.Item(Me.SetStateSubscription.SubscriptionId) = Me.SetStateSubscription
        End Sub

        Friend Sub DeleteSetStateEventQueue(ByVal context As ActivityExecutionContext)
            Me.Subscriptions.Item(Me.SetStateSubscription.SubscriptionId) = Nothing
            Me.SetStateSubscription.DeleteQueue(context)
        End Sub

        Friend Function Dequeue() As StateMachineSubscription
            Dim subscription As StateMachineSubscription = Me.EventQueue.Item(0)
            Me.EventQueue.RemoveAt(0)
            Return subscription
        End Function

        Friend Shared Sub DisableStateWorkflowQueues(ByVal context As ActivityExecutionContext, ByVal state As StateActivity)
            StateMachineSubscriptionManager.ChangeStateWorkflowQueuesState(context, state, False)
        End Sub

        Friend Sub Enqueue(ByVal context As ActivityExecutionContext, ByVal subscriptionId As Guid)
            Dim item As StateMachineSubscription = Me.GetSubscription(subscriptionId)
            If (Not item Is Nothing) Then
                Me.EventQueue.Add(item)
            End If
            Me.ProcessQueue(context)
        End Sub

        Friend Sub Enqueue(ByVal context As ActivityExecutionContext, ByVal queueName As IComparable)
            Dim item As StateMachineSubscription = Me.GetSubscription(queueName)
            If (Not item Is Nothing) Then
                Me.EventQueue.Add(item)
            End If
            Me.ProcessQueue(context)
        End Sub

        Private Shared Function GetQueueName(ByVal eventActivity As IEventActivity) As IComparable
            Return eventActivity.QueueName
        End Function

        Private Function GetSubscription(ByVal queueName As IComparable) As StateMachineSubscription
            Dim subscription As StateMachineSubscription
            Me.Subscriptions.TryGetValue(queueName, subscription)
            Return subscription
        End Function

        Private Function GetSubscription(ByVal eventActivity As IEventActivity) As EventActivitySubscription
            Dim queueName As IComparable = StateMachineSubscriptionManager.GetQueueName(eventActivity)
            If ((Not queueName Is Nothing) AndAlso Me.Subscriptions.ContainsKey(queueName)) Then
                Dim subscription As EventActivitySubscription = [TryCast](Me.Subscriptions.Item(queueName),EventActivitySubscription)
                Dim activity As Activity = DirectCast(eventActivity, Activity)
                If ((Not subscription Is Nothing) AndAlso Not (subscription.EventActivityName <> activity.QualifiedName)) Then
                    Return subscription
                End If
            End If
            Return Nothing
        End Function

        Private Function GetSubscriptionsShallowCopy() As Dictionary([Of] IComparable, StateMachineSubscription)
            Dim dictionary As New Dictionary([Of] IComparable, StateMachineSubscription)
            Dim pair As KeyValuePair([Of] IComparable, StateMachineSubscription)
            For Each pair In Me.Subscriptions
                dictionary.Add(pair.Key, pair.Value)
            Next
            Return dictionary
        End Function

        Friend Shared Function GetWorkflowQueue(ByVal context As ActivityExecutionContext, ByVal queueName As IComparable) As WorkflowQueue
            Dim service As WorkflowQueuingService = context.GetService([Of] WorkflowQueuingService)
            If service.Exists(queueName) Then
                Return service.GetWorkflowQueue(queueName)
            End If
            Return Nothing
        End Function

        Private Function IsEventDrivenSubscribed(ByVal eventDriven As EventDrivenActivity) As Boolean
            Dim eventActivity As IEventActivity = StateMachineHelpers.GetEventActivity(eventDriven)
            Return (Not Me.GetSubscription(eventActivity) Is Nothing)
        End Function

        Private Function IsParentState(ByVal state As StateActivity, ByVal stateName As String) As Boolean
            Dim activity As StateActivity = [TryCast](state.Parent,StateActivity)
            Do While (Not activity Is Nothing)
                If activity.QualifiedName.Equals(stateName) Then
                    Return True
                End If
                activity = [TryCast](activity.Parent,StateActivity)
            Loop
            Return False
        End Function

        Friend Sub ProcessQueue(ByVal context As ActivityExecutionContext)
            Dim currentState As StateActivity = StateMachineHelpers.GetCurrentState(context)
            If (((Me.EventQueue.Count <> 0) AndAlso Not Me.ExecutionState.HasEnqueuedActions) AndAlso (Not Me.ExecutionState.SchedulerBusy AndAlso (Not currentState Is Nothing))) Then
                Me.Dequeue.ProcessEvent(context)
            End If
        End Sub

        Friend Sub ReevaluateSubscriptions(ByVal context As ActivityExecutionContext)
            Dim subscriptionsShallowCopy As Dictionary([Of] IComparable, StateMachineSubscription) = Me.GetSubscriptionsShallowCopy
            Dim list As New List([Of] IComparable)
            Dim activity As StateActivity = StateMachineHelpers.GetCurrentState(context)
            Do While (Not activity Is Nothing)
                Dim activity2 As Activity
                For Each activity2 In activity.EnabledActivities
                    Dim eventDriven As EventDrivenActivity = [TryCast](activity2,EventDrivenActivity)
                    If (Not eventDriven Is Nothing) Then
                        Dim queueName As IComparable = StateMachineHelpers.GetEventActivity(eventDriven).QueueName
                        If (Not queueName Is Nothing) Then
                            Dim subscription As StateMachineSubscription
                            subscriptionsShallowCopy.TryGetValue(queueName, subscription)
                            Dim subscription2 As EventActivitySubscription = [TryCast](subscription,EventActivitySubscription)
                            If (Not subscription2 Is Nothing) Then
                                If subscription2.EventDrivenName.Equals(eventDriven.QualifiedName) Then
                                    list.Add(queueName)
                                    [Continue] For
                                End If
                                If subscription2.StateName.Equals(activity.QualifiedName) Then
                                    Throw New InvalidOperationException(SR.GetStateAlreadySubscribesToThisEvent(activity.QualifiedName, queueName))
                                End If
                                If Me.IsParentState(activity, subscription2.StateName) Then
                                    Dim action As New UnsubscribeAction(subscription2.StateName, subscription2.EventDrivenName)
                                    Me.ExecutionState.EnqueueAction(action)
                                    subscriptionsShallowCopy.Remove(queueName)
                                End If
                            End If
                            If Not list.Contains(queueName) Then
                                Dim action2 As New SubscribeAction(activity.QualifiedName, eventDriven.QualifiedName)
                                Me.ExecutionState.EnqueueAction(action2)
                                list.Add(queueName)
                            End If
                        End If
                    End If
                Next
                activity = [TryCast](activity.Parent,StateActivity)
            Loop
            Dim action3 As New DisableQueuesAction(StateMachineHelpers.GetCurrentState(context).QualifiedName)
            Me.ExecutionState.EnqueueAction(action3)
        End Sub

        Private Sub RemoveFromQueue(ByVal subscriptionId As Guid)
            Me.EventQueue.RemoveAll(Function (ByVal subscription As StateMachineSubscription) 
                Return subscription.SubscriptionId.Equals(subscriptionId)
            End Function)
        End Sub

        Private Function SubscribeEventActivity(ByVal context As ActivityExecutionContext, ByVal eventActivity As IEventActivity) As StateMachineSubscription
            Dim subscription As New EventActivitySubscription
            Dim state As StateActivity = DirectCast(context.Activity, StateActivity)
            subscription.Subscribe(context, state, eventActivity)
            Dim workflowQueue As WorkflowQueue = StateMachineSubscriptionManager.GetWorkflowQueue(context, subscription.QueueName)
            If (Not workflowQueue Is Nothing) Then
                workflowQueue.Enabled = True
            End If
            Me.Subscriptions.Item(subscription.QueueName) = subscription
            Return subscription
        End Function

        Friend Sub SubscribeEventDriven(ByVal context As ActivityExecutionContext, ByVal eventDriven As EventDrivenActivity)
            Dim eventActivity As IEventActivity = StateMachineHelpers.GetEventActivity(eventDriven)
            Dim activity1 As Activity = DirectCast(eventActivity, Activity)
            StateMachineSubscriptionManager.GetQueueName(eventActivity)
            Me.SubscribeEventActivity(context, eventActivity)
        End Sub

        Friend Sub SubscribeToSetStateEvent(ByVal context As ActivityExecutionContext)
            Me.SetStateSubscription.Subscribe(context)
            Me.Subscriptions.Item(Me.SetStateSubscription.SubscriptionId) = Me.SetStateSubscription
        End Sub

        Private Sub UnsubscribeEventActivity(ByVal context As ActivityExecutionContext, ByVal eventActivity As IEventActivity)
            If (context Is Nothing) Then
                Throw New ArgumentNullException("context")
            End If
            If (eventActivity Is Nothing) Then
                Throw New ArgumentNullException("eventActivity")
            End If
            Dim subscription As EventActivitySubscription = Me.GetSubscription(eventActivity)
            Dim workflowQueue As WorkflowQueue = StateMachineSubscriptionManager.GetWorkflowQueue(context, subscription.QueueName)
            If (Not workflowQueue Is Nothing) Then
                workflowQueue.Enabled = False
            End If
            Me.UnsubscribeEventActivity(context, eventActivity, subscription)
        End Sub

        Private Sub UnsubscribeEventActivity(ByVal context As ActivityExecutionContext, ByVal eventActivity As IEventActivity, ByVal subscription As EventActivitySubscription)
            If (context Is Nothing) Then
                Throw New ArgumentNullException("context")
            End If
            If (eventActivity Is Nothing) Then
                Throw New ArgumentNullException("eventActivity")
            End If
            If (subscription Is Nothing) Then
                Throw New ArgumentNullException("subscription")
            End If
            subscription.Unsubscribe(context, eventActivity)
            Me.RemoveFromQueue(subscription.SubscriptionId)
            Me.Subscriptions.Remove(subscription.QueueName)
        End Sub

        Friend Sub UnsubscribeEventDriven(ByVal context As ActivityExecutionContext, ByVal eventDriven As EventDrivenActivity)
            Dim eventActivity As IEventActivity = StateMachineHelpers.GetEventActivity(eventDriven)
            Me.UnsubscribeEventActivity(context, eventActivity)
        End Sub

        Friend Sub UnsubscribeState(ByVal context As ActivityExecutionContext)
            Dim activity As StateActivity = DirectCast(context.Activity, StateActivity)
            Dim activity2 As Activity
            For Each activity2 In activity.EnabledActivities
                Dim eventDriven As EventDrivenActivity = [TryCast](activity2,EventDrivenActivity)
                If ((Not eventDriven Is Nothing) AndAlso Me.IsEventDrivenSubscribed(eventDriven)) Then
                    Me.UnsubscribeEventDriven(context, eventDriven)
                End If
            Next
        End Sub

        Friend Sub UnsubscribeToSetStateEvent(ByVal context As ActivityExecutionContext)
            Me.Subscriptions.Item(Me.SetStateSubscription.SubscriptionId) = Nothing
            Me.SetStateSubscription.Unsubscribe(context)
        End Sub


        ' Properties
        Private ReadOnly Property EventQueue As List([Of] StateMachineSubscription)
            Get
                Return Me._eventQueue
            End Get
        End Property

        Friend ReadOnly Property ExecutionState As StateMachineExecutionState
            Get
                Return Me._executionState
            End Get
        End Property

        Friend ReadOnly Property SetStateSubscription As SetStateSubscription
            Get
                Return Me._setStateSubscription
            End Get
        End Property

        Friend ReadOnly Property Subscriptions As Dictionary([Of] IComparable, StateMachineSubscription)
            Get
                Return Me._subscriptions
            End Get
        End Property


        ' Fields
        Private _eventQueue As List([Of] StateMachineSubscription) = New List([Of] StateMachineSubscription)
        Private _executionState As StateMachineExecutionState
        Private _setStateSubscription As SetStateSubscription
        Private _subscriptions As Dictionary([Of] IComparable, StateMachineSubscription) = New Dictionary([Of] IComparable, StateMachineSubscription)
    End Class
End Namespace

